Willkomen zu einem weiteren aufregendem Tutorial! Der Code für dieses Tutorial wurde von Ben Humphrey geschrieben und basiert auf dem Framework aus Lektion 1. Von nun an, sollten Sie ein GL Experte sein {grins} und den Code in Ihren eigenen Basis-Code einzufügen, sollte ein Leichtes sein!
Dieses Tutorial bringt Ihnen bei, wie Sie ein cool aussehendes Terrain aus einer Height Map erzeugen. Für die unter Ihnen, die sich nichts unter einer Height Map vorstellen können, versuche ich eine grobe Erklärung abzugeben. Eine Height Map ist einfach... eine Ersetzung einer Oberfläche. Für die unter Ihnen, die sich immer noch den Kopf kratzen und sich selbst fragen "worüber redet der Kerl überhaup!?!"... Auf deutsch: unsere Height Map repräsentiert tiefe und hohe Punkte unserer Landschaft. Es liegt völlig bei Ihnen zu entscheiden, welche Schattierungen hohe Punkte und welche Schattierungen tiefe Punkte repräsentieren. Es ist auch wichtig zu erwähnen, dass Height Maps keine Bilder sein müssen... Sie können eine Height Map erzeugen, indem Sie einfach irgendwelche Daten verwenden. Sie können zum Beispiel einen Audio Stream verwenden um eine visuelle Height Map Repräsentation zu erzeugen. Wenn Sie immer noch verwirrt sind... lesesn Sie weiter... Sie werden es am Ende des Tutorials begriffen haben :)
#include < windows.h> // Header Datei für Windows #include < stdio.h> // Header Datei für Standard Input/Output ( NEW ) #include < gl\gl.h> // Header Datei für die OpenGL32 Library #include < gl\glu.h> // Header Datei für die GLu32 Library #include < gl\glaux.h> // Header Datei für die Glaux Library #pragma comment(lib, "opengl32.lib") // Linke OpenGL32.lib #pragma comment(lib, "glu32.lib") // Linke Glu32.lib
#define MAP_SIZE 1024 // Größe unsereR .RAW Height Map ( NEU ) #define STEP_SIZE 16 // Breite und Höhe jedes Quadrats ( NEU ) #define HEIGHT_RATIO 1.5f // Verhältnis indem Y zu X und Z skaliert wird ( NEU ) HDC hDC=NULL; // Privater GDI Device Context HGLRC hRC=NULL; // Permanenter Rendering Context HWND hWnd=NULL; // Enthält unser Fenster-Handle HINSTANCE hInstance; // Enthält die Instanz der Applikation bool keys[256]; // Array das für die Tastatur Routine verwendet wird bool active=TRUE; // Fenster Aktiv Flag ist standardmäßig auf TRUE gesetzt bool fullscreen=TRUE; // Fullscreen Flag ist standardmäßig auf TRUE gesetzt bool bRender = TRUE; // Polygon Flag ist standardmäßig auf TRUE gesetzt ( NEU )
BYTE g_HeightMap[MAP_SIZE*MAP_SIZE]; // enthält die Height Map Daten ( NEU ) float scaleValue = 0.15f; // Skalierungswert für das Terrain ( NEU ) LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Deklaration For WndProc
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // verändert die Größe und initialisiert das GL-Fenster
{
... SCHNITT ...
}
// Lädt die .RAW Datei und speichert sie in pHeightMap
void LoadRawFile(LPSTR strName, int nSize, BYTE *pHeightMap)
{
FILE *pFile = NULL;
// öffne die Datei zum Lesen im binären Modus.
pFile = fopen( strName, "rb" );
// Überprüfe ob wir die Datei gefunden haben und ob wir sie öffnen konnten
if ( pFile == NULL )
{
// Zeige Fehlernachricht an und stoppe die Funktion
MessageBox(NULL, "Konnte die Height Map nicht finden!", "Error", MB_OK);
return;
}
// Hier laden wir die .RAW Datei in unser pHeightMap Daten Array
// Wir lesen nur '1' ein und die Größe ist (Breite * Höhe)
fread( pHeightMap, 1, nSize, pFile );
// Nachdem wir die Daten gelesen haben, ist es eine gute Idee, zu überprüfen, ob beim Lesen alles glatt verlief
int result = ferror( pFile );
// überprüfe, ob ein Fehler aufgetreten ist
if (result)
{
MessageBox(NULL, "Daten konnten nicht geholt werden!!", "Error", MB_OK);
}
// Schließe die Datei
fclose(pFile);
}
int InitGL(GLvoid) // Der ganze Setup Kram für OpenGL kommt hier rein
{
glShadeModel(GL_SMOOTH); // aktiviert weiches Shading (Smooth Shading)
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Schwarzer Hintergrund
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // aktiviert Depth Testing
glDepthFunc(GL_LEQUAL); // Die Art des auszuführenden Depth Test
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // wirklich nette Perspektiven Berechnungen
// Hier lesen wir die Height Map aus der .RAW Datei ein und speichern sie in unserem
// g_HeightMap Array. Außerdem übergeben wir die Größe der .RAW Datei (1024).
LoadRawFile("Data/Terrain.raw", MAP_SIZE * MAP_SIZE, g_HeightMap); // ( NEU )
return TRUE; // Initialisierung war OK
}
int Height(BYTE *pHeightMap, int X, int Y) // Dies gibt die Höhe von einem Height Map Index zurück
{
int x = X % MAP_SIZE; // Überprüfe unseren X Wert
int y = Y % MAP_SIZE; // Überprüfe unseren Y Wert
if(!pHeightMap) return 0; // Stelle sicher, dass unsere Daten gültig sind
return pHeightMap[x + (y * MAP_SIZE)]; // Index in unser Height Array und gebe die Höhe zurück }
void SetVertexColor(BYTE *pHeightMap, int x, int y) // Dies setzt die Farbwerte für einen bestimmten Index
{ // Abhängig vom Height Index
if(!pHeightMap) return; // Stelle sicher, dass unsere Height Daten gültig sind
float fColor = -0.15f + (Height(pHeightMap, x, y ) / 256.0f);
// verbinde diese blaue Schattierung mit dem aktuellen Vertex
glColor3f(0.0f, 0.0f, fColor );
}
void RenderHeightMap(BYTE pHeightMap[]) // Damit rendern wir die Height Map als Quadrate
{
int X = 0, Y = 0; // erzeuge einige Variablen, um das Array zu durchlaufen.
int x, y, z; // erzeuge einige Variablen zur besseren Lesbarkeit
if(!pHeightMap) return; // Stelle sicher, dass unsere Height Daten gültig sind
if(bRender) // was wir rendern wollen glBegin( GL_QUADS ); // Rendere Polygons else glBegin( GL_LINES ); // Rendere Linien statt dessen
for ( X = 0; X < (MAP_SIZE-STEP_SIZE); X += STEP_SIZE )
for ( Y = 0; Y < (MAP_SIZE-STEP_SIZE); Y += STEP_SIZE )
{
// ermittle die (X, Y, Z) Werte für den unteren linken Vertex
x = X;
y = Height(pHeightMap, X, Y );
z = Y;
// Setze den Farbwert des aktuellen Vertex
SetVertexColor(pHeightMap, x, z);
glVertex3i(x, y, z); // Sende diesen Vertex an OpenGL zum rendern
// ermittle die (X, Y, Z) Werte für den oberen linken Vertex
x = X;
y = Height(pHeightMap, X, Y + STEP_SIZE );
z = Y + STEP_SIZE ;
// Setze den Farbwert des aktuellen Vertex
SetVertexColor(pHeightMap, x, z);
glVertex3i(x, y, z); // Sende diesen Vertex an OpenGL zum rendern
// ermittle die (X, Y, Z) Werte für den oberen rechten Vertex
x = X + STEP_SIZE;
y = Height(pHeightMap, X + STEP_SIZE, Y + STEP_SIZE );
z = Y + STEP_SIZE ;
// Setze den Farbwert des aktuellen Vertex
SetVertexColor(pHeightMap, x, z);
glVertex3i(x, y, z); // Sende diesen Vertex an OpenGL zum rendern
// ermittle die (X, Y, Z) Werte für den unteren rechten Vertex
x = X + STEP_SIZE;
y = Height(pHeightMap, X + STEP_SIZE, Y );
z = Y;
// Setze den Farbwert des aktuellen Vertex
SetVertexColor(pHeightMap, x, z);
glVertex3i(x, y, z); // Sende diesen Vertex an OpenGL zum rendern
}
glEnd();
glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // Resette die Farbe }
int DrawGLScene(GLvoid) // Hier kommt der ganze Zeichnen-Kram hin
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Lösche den Bildschirm und den Depth-Buffer
glLoadIdentity(); // Resette die Matrix
// Position Sicht Vektor der nach oben zeigt
gluLookAt(212, 60, 194, 186, 55, 171, 0, 1, 0); // dies bestimmt die Kamera Position und Sicht
glScalef(scaleValue, scaleValue * HEIGHT_RATIO, scaleValue);
RenderHeightMap(g_HeightMap); // Rendere die Height Map return TRUE; // Weiter geht's }
GLvoid KillGLWindow(GLvoid) // Entferne das Fenster korrekt
{
}
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
}
LRESULT CALLBACK WndProc( HWND hWnd, // Handle für dieses Fenster
UINT uMsg, // Nachricht für dieses Fenster
WPARAM wParam, // Weitere Nachrichten Informationen
LPARAM lParam) // Weitere Nachrichten Informationen
{
switch (uMsg) // Überprüfe auf Fenster-Nachrichten
{
case WM_ACTIVATE: // Wenn Aktivierungs-Nachricht reinkommt
{
if (!HIWORD(wParam)) // Überprüfe Minimierungs-Status
{
active=TRUE; // Programm ist aktiv
}
else
{
active=FALSE; // Programm ist nicht länger aktiv
}
return 0; // Kehre zurück zur Nachrichten-Schleife
}
case WM_SYSCOMMAND: // hereinkommende System Befehle
{
switch (wParam) // Überprüfe auf System Aufrufe
{
case SC_SCREENSAVE: // versucht der Screensaver sich zu starten?
case SC_MONITORPOWER: // versucht der Monitor in den Energiesparmodus zu gehen?
return 0; // verhindere das
}
break; // beende
}
case WM_CLOSE: // Haben wir eine Nachricht zum Schließen erhalten?
{
PostQuitMessage(0); // Sende eine Beenden-Nachricht
return 0; // Springe zurück
}
case WM_LBUTTONDOWN: // haben wir einen linken Mausklick erhalten?
{
bRender = !bRender; // Ändere Render-Status zwischen Gefüllt/Drahtgittermodell
return 0; // Springe zurück
}
case WM_KEYDOWN: // Wird eine Taste gedrückt?
{
keys[wParam] = TRUE; // Wenn ja, markiere sie mit TRUE
return 0; // Springe zurück
}
case WM_KEYUP: // Wurde eine Taste losgelassen?
{
keys[wParam] = FALSE; // Wenn ja, markiere sie mit FALSE
return 0; // Springe zurück
}
case WM_SIZE: // ändere die Größe des Fenster
{
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Breite, HiWord=Höhe
return 0; // Springe zurück
}
}
// Übergebe alle nicht bearbeiteten Nachrichten an DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
int WINAPI WinMain( HINSTANCE hInstance, // Instanz
HINSTANCE hPrevInstance, // vorherige Instanz
LPSTR lpCmdLine, // Kommandozeilen Parameter
int nCmdShow) // Fenster Anzeige Status
{
MSG msg; // Windows Nachrichten Struktur
BOOL done=FALSE; // Bool Variable um die Schleife zu beenden
// Frage den Benutzer, in welchen Modus er starten will
if (MessageBox(NULL,"Wollen Sie im Vollbildmodus starten?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // Fenster-Modus
}
// erzeuge unser OpenGL Fenster
if (!CreateGLWindow("NeHe & Ben Humphrey's Height Map Tutorial", 640, 480, 16, fullscreen))
{
return 0; // Beende, wenn Fenster nicht erzeugt wurde
}
while(!done) // Schleife die so lange läuft, bis done=TRUE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Wartet eine Nachricht?
{
if (msg.message==WM_QUIT) // Haben wir eine Nachricht zum beenden erhalten?
{
done=TRUE; // Wenn ja done=TRUE
}
else // Wenn nicht, bearbeite die Fenster-Nachrichten
{
TranslateMessage(&msg); // Übersetze die Nachricht
DispatchMessage(&msg); // bearbeite die Nachricht
}
}
else // Wenn da keine Nachrichten sind
{
// Zeichne die Szene. Schau nach der ESC-Taste und Beendigungs-Nachrichten von DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Acktiv? Wurde Signal zum Beenden gesendet?
{
done=TRUE; // ESC oder DrawGLScene signalisiert uns, dass beendet werden soll
}
else if (active) // es ist noch nicht Zeit, zum beenden, aktualisiere den Screen
{
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}
if (keys[VK_F1]) // Wurde F1 gedrückt?
{
keys[VK_F1]=FALSE; // Wenn ja, setze Taste auf FALSE
KillGLWindow(); // Kill unser aktuelles Fenster
fullscreen=!fullscreen; // Wechsel zwischen Fullscreen und Fester-Modus
// Erzeuge unser OpenGL Fenster erneut
if (!CreateGLWindow("NeHe & Ben Humphrey's Height Map Tutorial", 640, 480, 16, fullscreen))
{
return 0; // Beenden, wenn das Fenster nicht erzeugt wurde
}
}
if (keys[VK_UP]) // wurde die Pfeil-Hoch-Taste gedrückt? scaleValue += 0.001f; // Inkrementiere den Skalierungswert um heran zu zoomen if (keys[VK_DOWN]) // Wurde die Pfeil-Runter-Taste gedrückt? scaleValue -= 0.001f; // dekrementiere den Skalierungswert um raus zu zoomen } } // Shutdown KillGLWindow(); // Kill das Fenster return (msg.wParam); // Beende das Programm }